// (C) Copyright 2006 Marvell International Ltd.
// All Rights Reserved
//

#include "gcu_client.h"

enum
{
    GC_BBST = 0x00000000,	/* Batch Buffer Start */
    GC_BBEND = 0x01000000,	/* Batch Buffer End */
    GC_BUFFI = 0x02000000,	/* Buffer Info */
    GC_LREG = 0x04000000,	/* Load Register */
    GC_NOP = 0x05000000,	/* NOP */
    GC_DBFLIP = 0x06000000,	/* Destination Buffer Flip */
    GC_STREG = 0x07000000,	/* Store Register */
    GC_INT = 0x08000000,	/* Interrupt to Core */
    GC_WAIT = 0x09000000,	/* Wait for Event */

    GC_CFILL_IMM = 0x40000010,	/* Color Fill (Immediate Color) */
    GC_CFILL_REG = 0x40000000,	/* Color Fill (Color in register) */
    GC_CKBLT = 0x41000000,	/* Chroma Key BLT */
    GC_LINE_IMM = 0x42000010,	/* Line Draw  (Immediate Color) */
    GC_LINE_REG = 0x42000000,	/* Line Draw  (Color in register) */
    GC_AALINE_IMM = 0x43000010,	/* Anti-alias Line Draw */
    GC_AALINE_REG = 0x43000000,	/* Anti-alias Line Draw */
    GC_STRBLT = 0x45000000,	/* Stretch BLT */
    GC_ABLND = 0x47000000,	/* Alpha Blend BLT */
    GC_SCALE = 0x48000000,	/* Scale BLT */
    GC_BIAS = 0x49000000,	/* Bias BLT */
    GC_ROTATE = 0x4a000000,     /* Rotate */
    GC_RASTER = 0x4b000000,	/* Raster OP BLT */
    GC_PATT = 0x4c000000,	/* Pattern Copy BLT */
    GC_DECBLT = 0x4d000000,	/* Decimate BLT */
};


LONG width(RECTL rect)
{
    return rect.right - rect.left;
}

LONG height(RECTL rect)
{
    return rect.bottom - rect.top;
}

bool GCUClient::color_fill(GCUColor color, RECTL rect)
{
    UINT32 w, h;

    // don't support 48/64 bit color
    if (color.pixel_format.rgb_format >= 0xa)
        return 0;

    w = width(rect);
    h = height(rect);

    if (rect.left > 0x7ff || rect.top > 0x7ff
        || w > 0x7ff || h > 0x7ff)
        return 0;

    UINT32* buf = cmd_buf.alloc(5);

    if (!buf)
        return 0;

    buf[0] = GC_CFILL_IMM | (color.pixel_format.rgb_format << 8);
    buf[1] = rect.left;
    buf[2] = rect.top;
    buf[3] = (h << 16) | w;
        
    buf[0] |= 0x4;
    buf[4] = color.color;
    return 1;
}

bool GCUClient::stretch(RECTL src_rect, RECTL dst_rect)
{
    LONG src_w = width(src_rect);
    LONG src_h = height(src_rect);
    LONG dst_w = width(dst_rect);
    LONG dst_h = height(dst_rect);

    UINT max_width_tbl[] = {
                                0,
                                497,
                                249,
                                167,
                                126,    // 4
                                0,
                                0,
                                0,
                                0
                            };

    if ((UINT)src_w > max_width_tbl[context.get_data_buf(src0)->format.step])
        return 0;

    if (src_h > 0x7ff 
        || dst_h > 0x7ff || dst_w > 0x7ff 
        || dst_rect.left > 0x7ff || dst_rect.top > 0x7ff
        || src_rect.left > 0x7ff || src_rect.top > 0x7ff)
        return 0;

    UINT32 x_str = ((src_w - 1) << 16) / (dst_w - 1);
    UINT32 y_str = ((src_h - 1) << 16) / (dst_h - 1);

    if (x_str > 0x3ffffff || y_str > 0x3ffffff)
        return 0;

    UINT32* buf = cmd_buf.alloc(9);

    if (!buf)
        return 0;

    buf[0] = GC_STRBLT | 0x8;
    buf[1] = dst_rect.left;
    buf[2] = dst_rect.top;
    buf[3] = src_rect.left;
    buf[4] = src_rect.top;
    buf[5] = (src_h << 16) | (src_w & 0x1ff);
    buf[6] = (dst_h << 16) | (dst_w & 0xffff);
    buf[7] = (x_str & 0x03ff0000) | ((x_str >> 6) & 0x3ff);
    buf[8] = (y_str & 0x03ff0000) | ((y_str >> 6) & 0x3ff);
    return 1;
}


bool GCUClient::decimate(RECTL src_rect, RECTL dst_rect)
{
    UINT32* buf = cmd_buf.alloc(9);

    if (!buf)
        return 0;

    LONG src_w = width(src_rect);
    LONG src_h = height(src_rect);
    LONG dst_w = width(dst_rect);
    LONG dst_h = height(dst_rect);

    UINT32 x_dec = ((src_w - 1) << 16) / (dst_w - 1);
    UINT32 y_dec = ((src_h - 1) << 16) / (dst_h - 1);

    buf[0] = GC_DECBLT | 0x8;
    buf[1] = dst_rect.left;
    buf[2] = dst_rect.top;
    buf[3] = src_rect.left;
    buf[4] = src_rect.top;
    buf[5] = (height(src_rect) << 16) | (width(src_rect) & 0x1ff);
    buf[6] = (height(dst_rect) << 16) | (width(dst_rect) & 0x1ff);
    buf[7] = (x_dec & 0x03ff0000) | ((x_dec >> 6) & 0x3ff);
    buf[8] = (y_dec & 0x03ff0000) | ((y_dec >> 6) & 0x3ff);
    return 1;
}

bool GCUClient::raster_op(UINT32 op, 
              POINTL src0_p, 
              POINTL src1_p, 
              POINTL dst_p, 
              SIZEL size)
{
    if (src0_p.x > 0x7ff || src0_p.y > 0x7ff
        || src1_p.x > 0x7ff || src1_p.y > 0x7ff
        || dst_p.x > 0x7ff || dst_p.y > 0x7ff
        || size.cx > 0x7ff || size.cy > 0x7ff)
        return 0;

    UINT32* buf = cmd_buf.alloc(8);

    if (!buf)
        return 0;

    buf[0] = GC_RASTER | (op << 16) | 0x07;
    buf[1] = dst_p.x; 
    buf[2] = dst_p.y;					
    buf[3] = src0_p.x;
    buf[4] = src0_p.y;					
    buf[5] = src1_p.x; 
    buf[6] = src1_p.y;					
    buf[7] = (size.cy << 16) | (size.cx);	
    return 1;
}

bool GCUClient::rotate(UINT32 rotate,
                       bool align_32bytes,
                       POINTL src_p, 
                       POINTL dst_p, 
                       SIZEL size)
{
    if (src_p.x > 0x7ff || src_p.y > 0x7ff
        || dst_p.x > 0x7ff || dst_p.y > 0x7ff
        || size.cx > 0x7ff || size.cy > 0x7ff)
        return 0;

    UINT32* buf = cmd_buf.alloc(6);

    if (!buf)
        return 0;

    buf[0] = GC_ROTATE | (align_32bytes << 6) | (rotate << 4) | 0x05;
    buf[1] = dst_p.x; 
    buf[2] = dst_p.y;					
    buf[3] = src_p.x;
    buf[4] = src_p.y;					
    buf[5] = (size.cy << 16) | (size.cx);	
    return 1;
}

bool GCUClient::alpha_blend(bool is_const_alpha,
                            UINT32 alpha, 
                            POINTL src0_p, 
                            POINTL src1_p, 
                            POINTL dst_p, 
                            SIZEL size)
{
    if (src0_p.x > 0x7ff || src0_p.y > 0x7ff
        || src1_p.x > 0x7ff || src1_p.y > 0x7ff
        || dst_p.x > 0x7ff || dst_p.y > 0x7ff
        || size.cx > 0x7ff || size.cy > 0x7ff)
        return 0;
 
    UINT cmd_size = is_const_alpha? 9 : 8;

    UINT32* buf = cmd_buf.alloc(cmd_size);

    if (!buf)
        return 0;

    buf[0] = GC_ABLND | (1 << 8) | (cmd_size - 1);

    if (is_const_alpha)
    {
        buf[0] |= 1 << 4;
        buf[8] = alpha << 16;
    }

    buf[1] = dst_p.x; 
    buf[2] = dst_p.y;
    buf[3] = src0_p.x;
    buf[4] = src0_p.y;
    buf[5] = src1_p.x; 
    buf[6] = src1_p.y;
    buf[7] = (size.cy << 16) | size.cx;

    return 1;
}

static void octant_dir_convert(int dir, 
                        UINT32* x_dir, 
                        UINT32* y_dir,
                        UINT32* x_is_1,
                        UINT32* y_is_1)
{
    *y_dir = dir <= 3 ? 0 : 1;
    *x_dir = dir <= 1 || dir >= 6 ? 1 : 0;

    *x_is_1 = dir == 0 || dir == 7 || dir == 3 || dir == 4 ? 1 : 0;
    *y_is_1 = *x_is_1? 0 : 1;
}

bool GCUClient::draw_line_vertical_horizontal(GCUColor color, 
                                              UINT32 dir, 
                                              POINTL start, 
                                              UINT32 length)
{
    if (length > 0x7ff)
        return 0;
    
    UINT32* buf = cmd_buf.alloc(6);
    
    if (!buf)
        return 0;

    UINT32 x_dir; 
    UINT32 y_dir;
    UINT32 x_is_1;
    UINT32 y_is_1;

    octant_dir_convert(dir, &x_dir, &y_dir, &x_is_1, &y_is_1);

    buf[0] = GC_LINE_IMM | (color.pixel_format.rgb_format << 8) | 0x05;
    buf[1] = start.x;
    buf[2] = start.y;
    buf[3] = (x_dir << 31) | (x_is_1 << 30)| (y_dir << 15) | (y_is_1 << 14);
    buf[4] = (1 << 16) | length;
    buf[5] = color.color;
    return 1;
}

static void buf_info_cmd_make(UINT32* cmd, 
                              GCUDataBufId gcu_buf_id, 
                              UINT32 frame, 
                              GCUPixelFormat format,
                              UINT32 stride
                             )
{
    // mapping of gcu buffer address
    UINT gcu_buf_addr[] = 
    { 
        0,
        1,
        8,
        9,
        10,
        11
    };

    cmd[0] = GC_BUFFI | (gcu_buf_addr[gcu_buf_id] << 4) | 0x2;
    cmd[1] = frame;
    cmd[2] = (format.rgb_format << 19) | (stride << 5) | format.step;
}

void dump_gcu_data_buf(GCUDataBuf* data)
{
    NKDbgPrintfW(TEXT("base %x!\r\n"), data->base);
    NKDbgPrintfW(TEXT("format %x, %x!\r\n"), data->format.rgb_format, data->format.step);
    NKDbgPrintfW(TEXT("stride %x!\r\n"), data->stride);
}

bool GCUClient::load_buf(GCUDataBufId gcu_buf_id, GCUDataBuf* buf)
{
    GCUDataBuf* old_buf = context.get_data_buf(gcu_buf_id);

    //dump_gcu_data_buf(old_buf);
    //dump_gcu_data_buf(buf);


    if (memcmp(old_buf, buf, sizeof(*buf)) == 0)
        return 1;

    if (buf->stride > (1 << 14) - 1)
        return 0;

    UINT32* cmd = cmd_buf.alloc(buf_info_cmd_size);

    if (!cmd)
        return 0;

    buf_info_cmd_make(cmd, gcu_buf_id, buf->base, buf->format, buf->stride);
 
    context.set_data_buf(gcu_buf_id, buf, cmd);
    return 1;
}


bool GCUClient::submit()
{
    UINT32* context_buf = 0;
    UINT context_size = 0;

    if (!cmd_buf.get_size())
        return 1;

    // add a nop
    UINT32* nop_cmd = cmd_buf.alloc(1);
    
    if (nop_cmd)
        *nop_cmd = GC_NOP;

    if (!context.get_sync_flag())
    {
        context_buf = context.get_cmd_buf()->get_buf();
        context_size = context.get_cmd_buf()->get_size();
    }

    bool rv = proxy->submit(context_buf, context_size, 
                            cmd_buf.get_buf(), cmd_buf.get_size());

    if (rv)
        context.set_sync_flag(1);

    cmd_buf.reset();

    if (rv == 0)
        NKDbgPrintfW(TEXT("gcu client submit failed!\r\n"));

    return rv;
}

void GCUClient::sync()
{
    WaitForSingleObject(sync_event, INFINITE);
}

bool GCUClient::is_sync()
{
    DWORD rv = WaitForSingleObject(sync_event, 0);

    return rv == WAIT_OBJECT_0;
}

GCUClient::GCUClient(GCUClientProxy* the_proxy, UINT cmd_buf_size) : 
    proxy(the_proxy),
    cmd_buf(cmd_buf_size)
{
    sync_event = OpenEvent(EVENT_ALL_ACCESS, FALSE, sync_event_name);

    // self test with nop
    UINT32* buf = cmd_buf.alloc(10);
    for (UINT i = 0; i < 10; i++)
        buf[i] = GC_NOP;

    submit();
}

GCUClient::~GCUClient()
{
}

GCUContext::GCUContext() : cmd_buf(context_cmd_size)
{


    UINT i;
    for (i = 0; i < n_gcu_data_bufs; i++)
    {
        UINT32* buf = cmd_buf.alloc(buf_info_cmd_size);



        buf[0] = GC_NOP;
        buf[1] = GC_NOP;
        buf[2] = GC_NOP;
    }
    sync_flag = 1;
}

GCUCmdBuf* GCUContext::get_cmd_buf()
{
    return &cmd_buf;
}

GCUDataBuf* GCUContext::get_data_buf(GCUDataBufId id)
{
    return &data_bufs[id];
}

void GCUContext::set_data_buf(GCUDataBufId id, GCUDataBuf* buf, UINT32* cmd)
{
    sync_flag = 0;

    data_bufs[id] = *buf;

    UINT32* context_cmd_buf = cmd_buf.get_buf();
    context_cmd_buf += id * buf_info_cmd_size;
    context_cmd_buf[0] = cmd[0];
    context_cmd_buf[1] = cmd[1];
    context_cmd_buf[2] = cmd[2];
}

void GCUContext::set_sync_flag(bool flag)
{
    sync_flag = flag;
}

bool GCUContext::get_sync_flag()
{
    return sync_flag;
}
